{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5e3856e6",
   "metadata": {},
   "source": [
    "# LangGraph Streaming Outputs\n",
    "\n",
    "- Author: [hong-seongmin](https://github.com/hong-seongmin)\n",
    "- Peer Review: \n",
    "- Proofread : [Chaeyoon Kim](https://github.com/chaeyoonyunakim)\n",
    "- This is a part of [LangChain OpenTutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/01-Core-Features/05-LangGraph-Streaming-Outputs.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/17-LangGraph/01-Core-Features/05-LangGraph-Streaming-Outputs.ipynb)\n",
    "\n",
    "## Overview\n",
    "\n",
    "**LangGraph Streaming Outputs** explores the step-by-step streaming capabilities within LangGraph. \n",
    "\n",
    "This approach allows developers to observe and interact with each graph processing stage in real-time, offering insights into the internal workings and progress of graph computations.\n",
    "\n",
    "- **Step-by-Step Streaming**  \n",
    "  The streaming functionality breaks down the graph execution into individual steps, providing detailed outputs at each stage. This makes it easier to debug and understand how data flows through the graph, as well as how decisions are made along the way.\n",
    "\n",
    "- **Real-Time Feedback**  \n",
    "  By leveraging streaming outputs, users receive immediate feedback from the system. This real-time interaction can greatly enhance the debugging process and the ability to fine-tune graph configurations on the fly.\n",
    "\n",
    "- **Enhanced Transparency and Control**  \n",
    "  Streaming outputs offer greater transparency into the execution of LangGraph pipelines. Users can monitor each transformation or computation, enabling more precise control over the process and facilitating a deeper comprehension of complex graph-based workflows.\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environment Setup](#environment-setup)\n",
    "- [Step-by-step Streaming Output with LangGraph](#Step-by-step-Streaming-Output-with-LangGraph)\n",
    "- [The stream Method of StateGraph](#The-stream-Method-of-StateGraph)\n",
    "- [Advanced Streaming Options](#advanced-streaming-options)\n",
    "\n",
    "### References\n",
    "\n",
    "- [LangChain Documentation: LangGraph](https://langchain-ai.github.io/langgraph/tutorials/introduction/)\n",
    "- [LangChain Documentation: Streaming](https://python.langchain.com/docs/concepts/streaming/)\n",
    "- [LangChain-ai Github: LangGraph Streaming](https://langchain-ai.github.io/langgraph/concepts/streaming/)\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d94c995e",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "3c8898f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "fd66580f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [\n",
    "        \"langchain\",\n",
    "        \"langchain_core\",\n",
    "        \"langchain_openai\",\n",
    "        \"langgraph\",\n",
    "        \"GoogleNews\"\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "015518d9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Environment variables have been set successfully.\n"
     ]
    }
   ],
   "source": [
    "# Set environment variables\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"LANGCHAIN_TRACING_V2\": \"true\",\n",
    "        \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n",
    "        \"LANGCHAIN_PROJECT\": \"10-LangGraph-Streaming-Outputs\",\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3863fcd1",
   "metadata": {},
   "source": [
    "Alternatively, environment variables can also be set using a ```.env``` file.\n",
    "\n",
    "**[Note]**\n",
    "\n",
    "- This is not necessary if you've already set the environment variables in the previous step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "77848329",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Configuration file to manage API keys as environment variables\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# Load API key information\n",
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eec680f5",
   "metadata": {},
   "source": [
    "## Step-by-step Streaming Output with LangGraph\n",
    "\n",
    "This time, we'll take a closer look at the ```stream()``` output function in LangGraph.\n",
    "\n",
    "LangGraph's streaming output function provides the ability to stream each step of the graph.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b2184f4",
   "metadata": {},
   "source": [
    "Note: The LangGraph example below is identical to the example from the previous section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "51549b1d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from typing import Annotated, List, Dict\n",
    "from typing_extensions import TypedDict\n",
    "\n",
    "from langchain_core.tools import tool\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "from langgraph.prebuilt import ToolNode, tools_condition\n",
    "from GoogleNews import GoogleNews  # Using GoogleNews library for news search\n",
    "\n",
    "# Import for Mermaid visualization\n",
    "from IPython.display import Image, display\n",
    "from langchain_core.runnables.graph import MermaidDrawMethod\n",
    "\n",
    "########## 1. State Definition ##########\n",
    "# Define state\n",
    "class State(TypedDict):\n",
    "    # Add comment for the list of messages\n",
    "    messages: Annotated[list, add_messages]\n",
    "    dummy_data: Annotated[str, \"dummy\"]\n",
    "\n",
    "########## 2. Tool Definition and Binding ##########\n",
    "# Tool initialization\n",
    "# Create a tool to search news by keyword using GoogleNews\n",
    "news_tool = GoogleNews()\n",
    "\n",
    "@tool\n",
    "def search_keyword(query: str) -> List[Dict[str, str]]:\n",
    "    \"\"\"Look up news by keyword\"\"\"\n",
    "    news_tool = GoogleNews()\n",
    "    news_tool.search(query)\n",
    "    results = news_tool.results(sort=True)\n",
    "    # Limit to top 5 results\n",
    "    limited_results = results[:5] if results else []\n",
    "    # Extract title and link from each result\n",
    "    return [{\"title\": item[\"title\"], \"link\": item[\"link\"]} for item in limited_results]\n",
    "\n",
    "tools = [search_keyword]\n",
    "\n",
    "# Initialize the LLM\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "\n",
    "# Combine tools with LLM\n",
    "llm_with_tools = llm.bind_tools(tools)\n",
    "\n",
    "########## 3. Add Nodes ##########\n",
    "# Define a chatbot function\n",
    "def chatbot(state: State):\n",
    "    # Invoke and return messages\n",
    "    return {\n",
    "        \"messages\": [llm_with_tools.invoke(state[\"messages\"])],\n",
    "        \"dummy_data\": \"[chatbot] called, dummy data\",  # Add dummy data for testing\n",
    "    }\n",
    "\n",
    "# Create a state graph\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "# Add chatbot node\n",
    "graph_builder.add_node(\"chatbot\", chatbot)\n",
    "\n",
    "# Create and add a tool node\n",
    "tool_node = ToolNode(tools=tools)\n",
    "graph_builder.add_node(\"tools\", tool_node)\n",
    "\n",
    "# Conditional edges\n",
    "graph_builder.add_conditional_edges(\n",
    "    \"chatbot\",\n",
    "    tools_condition,\n",
    ")\n",
    "\n",
    "########## 4. Add Edges ##########\n",
    "\n",
    "# tools > chatbot\n",
    "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
    "\n",
    "# START > chatbot\n",
    "graph_builder.add_edge(START, \"chatbot\")\n",
    "\n",
    "# chatbot > END\n",
    "graph_builder.add_edge(\"chatbot\", END)\n",
    "\n",
    "########## 5. Compile the Graph ##########\n",
    "\n",
    "# Compile the graph builder\n",
    "graph = graph_builder.compile()\n",
    "\n",
    "########## 6. Visualize the Graph with Mermaid ##########\n",
    "# Visualize the graph using Mermaid\n",
    "display(\n",
    "    Image(\n",
    "        graph.get_graph().draw_mermaid_png(\n",
    "            draw_method=MermaidDrawMethod.API,\n",
    "        )\n",
    "    )\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bea7dc51",
   "metadata": {},
   "source": [
    "## The stream Method of StateGraph\n",
    "\n",
    "The ```stream``` method provides the ability to stream graph steps for a single input.\n",
    "\n",
    "**Parameters**\n",
    "- ```input``` (```Union[dict[str, Any], Any]``` ) : Input to the graph\n",
    "- ```config``` (```Optional[RunnableConfig]``` ) : Execution configuration\n",
    "- ```stream_mode``` (```Optional[Union[StreamMode, list[StreamMode]]]``` ) : Output streaming mode\n",
    "- ```output_keys``` (```Optional[Union[str, Sequence[str]]]``` ) : Keys to stream\n",
    "- ```interrupt_before``` (```Optional[Union[All, Sequence[str]]]``` ) : Nodes to interrupt before execution\n",
    "- ```interrupt_after``` (```Optional[Union[All, Sequence[str]]]``` ) : Nodes to interrupt after execution\n",
    "- ```debug``` (```Optional[bool]``` ) : Whether to output debug information\n",
    "- ```subgraphs``` (```bool```) : Whether to stream subgraphs\n",
    "\n",
    "**Returns**\n",
    "- ```Iterator[Union[dict[str, Any], Any]]``` : Outputs from each step of the graph. The output format depends on ```stream_mode``` .\n",
    "\n",
    "**Key Features**\n",
    "1. Processes graph execution in a streaming manner according to the given configuration\n",
    "2. Supports various streaming modes (```values``` , ```updates``` , ```debug```)\n",
    "3. Manages callbacks and error handling\n",
    "4. Handles recursion limits and interruption conditions\n",
    "\n",
    "**Streaming Modes**\n",
    "- ```values``` : Outputs the current state values at each step\n",
    "- ```updates``` : Outputs only state updates at each step\n",
    "- ```debug``` : Outputs debug events at each step"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "3ae917b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnableConfig\n",
    "\n",
    "# Question\n",
    "question = \"Please provide the latest news about the 2024 Nobel Prize in Literature.\"\n",
    "\n",
    "# Define the initial input state\n",
    "input = State(dummy_data=\"Test string\", messages=[(\"user\", question)])\n",
    "\n",
    "# Configure config\n",
    "config = RunnableConfig(\n",
    "    recursion_limit=10,  # Visit up to 10 nodes; beyond that, RecursionError will occur\n",
    "    configurable={\"thread_id\": \"1\"},  # Set the thread ID\n",
    "    tags=[\"my-tag\"],  # Tag\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e931823b",
   "metadata": {},
   "source": [
    "We set up the ```config``` and proceed with streaming output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "ab5c0bad",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[ chatbot ]\n",
      "\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  search_keyword (call_a9QA6AiVxJjPvTJbd92lPbiw)\n",
      " Call ID: call_a9QA6AiVxJjPvTJbd92lPbiw\n",
      "  Args:\n",
      "    query: 2024 Nobel Prize in Literature\n",
      "\n",
      "[ tools ]\n",
      "\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: search_keyword\n",
      "\n",
      "[{\"title\": \"We Do Not Part is Han Kang’s most ambitious work yet\", \"link\": \"https://chicagoreader.com/books/book-review/we-do-not-part-han-kang/&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQICBAC&usg=AOvVaw17aQ9BgzWGdDWg5upvavvj\"}, {\"title\": \"2024 Nobel Prize for Literature #KoreaNetPerson of the Year #Vegetarian #TheBoy Is ComingNovelist H..\", \"link\": \"https://www.mk.co.kr/en/culture/11206720&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQICRAC&usg=AOvVaw3ZUIOTxml6j8wvurAioOGU\"}, {\"title\": \"S. Korea's Han Kang receives Nobel literature prize amid turmoil at home\", \"link\": \"https://english.kyodonews.net/news/2024/12/45926507c4d5-s-korea-writer-receives-nobel-prize-amid-political-turmoil-at-home.html&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQIBBAC&usg=AOvVaw3egcTV9I3PqS2-ES3hqtqc\"}, {\"title\": \"Olga Tokarczuk attends 2024 Nobel Prize ceremony as special guest - English Section\", \"link\": \"https://www.polskieradio.pl/395/7791/artykul/3457545,olga-tokarczuk-attends-2024-nobel-prize-ceremony-as-special-guest&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQIABAC&usg=AOvVaw3-wDuRXlMqzlno72eEe8WL\"}, {\"title\": \"Han Kang asserts literature combats life-destructive acts - CHOSUNBIZ\", \"link\": \"https://biz.chosun.com/en/en-society/2024/12/11/CAGKFC32LFEN3DIXJH4HI72M64/&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQIARAC&usg=AOvVaw3zOSaUYmkiuOyM8xzYOMz3\"}]\n",
      "\n",
      "[ chatbot ]\n",
      "\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Here are the latest news articles related to the 2024 Nobel Prize in Literature:\n",
      "\n",
      "1. **We Do Not Part is Han Kang’s most ambitious work yet**  \n",
      "   [Read more](https://chicagoreader.com/books/book-review/we-do-not-part-han-kang/&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQICBAC&usg=AOvVaw17aQ9BgzWGdDWg5upvavvj)\n",
      "\n",
      "2. **2024 Nobel Prize for Literature #KoreaNetPerson of the Year #Vegetarian #TheBoy Is Coming Novelist H..**  \n",
      "   [Read more](https://www.mk.co.kr/en/culture/11206720&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQICRAC&usg=AOvVaw3ZUIOTxml6j8wvurAioOGU)\n",
      "\n",
      "3. **S. Korea's Han Kang receives Nobel literature prize amid turmoil at home**  \n",
      "   [Read more](https://english.kyodonews.net/news/2024/12/45926507c4d5-s-korea-writer-receives-nobel-prize-amid-political-turmoil-at-home.html&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQIBBAC&usg=AOvVaw3egcTV9I3PqS2-ES3hqtqc)\n",
      "\n",
      "4. **Olga Tokarczuk attends 2024 Nobel Prize ceremony as special guest**  \n",
      "   [Read more](https://www.polskieradio.pl/395/7791/artykul/3457545,olga-tokarczuk-attends-2024-nobel-prize-ceremony-as-special-guest&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQIABAC&usg=AOvVaw3-wDuRXlMqzlno72eEe8WL)\n",
      "\n",
      "5. **Han Kang asserts literature combats life-destructive acts**  \n",
      "   [Read more](https://biz.chosun.com/en/en-society/2024/12/11/CAGKFC32LFEN3DIXJH4HI72M64/&ved=2ahUKEwikieP5xImLAxUvsFYBHd8MEEMQxfQBegQIARAC&usg=AOvVaw3zOSaUYmkiuOyM8xzYOMz3)\n",
      "\n",
      "These articles cover various aspects of the event and the laureate, Han Kang.\n"
     ]
    }
   ],
   "source": [
    "for event in graph.stream(input=input, config=config):\n",
    "    for key, value in event.items():\n",
    "        print(f\"\\n[ {key} ]\\n\")\n",
    "        # If messages exist in value\n",
    "        if \"messages\" in value:\n",
    "            messages = value[\"messages\"]\n",
    "            # Print only the most recent message\n",
    "            value[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0d00b51",
   "metadata": {},
   "source": [
    "## Advanced Streaming Options\n",
    "\n",
    "Advanced streaming options in LangGraph allow for more fine-grained control over how graph execution outputs are handled. By configuring parameters like ```output_keys```, ```stream_mode```, ```interrupt_before```, and ```interrupt_after```, users can tailor the streaming behavior to their specific debugging and monitoring needs.\n",
    "\n",
    "\n",
    "### The output_keys Option\n",
    "\n",
    "The ```output_keys``` option is used to specify which keys to stream.\n",
    "\n",
    "You can specify it in list format, and it must be one of the keys defined in **channels**.\n",
    "\n",
    "**Tip**\n",
    "\n",
    "- If a large number of State keys is output at each step, this is useful when you only want to stream some of them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "efe63155",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['messages', 'dummy_data', '__start__', 'chatbot', 'tools', 'branch:__start__:__self__:chatbot', 'branch:__start__:__self__:tools', 'branch:chatbot:__self__:chatbot', 'branch:chatbot:__self__:tools', 'branch:tools:__self__:chatbot', 'branch:tools:__self__:tools', 'start:chatbot', 'branch:chatbot:tools_condition:tools']\n"
     ]
    }
   ],
   "source": [
    "# Print the list of keys defined in channels\n",
    "print(list(graph.channels.keys()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "87efc73e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[ chatbot ]\n",
      "\n",
      "dict_keys(['dummy_data'])\n",
      "[chatbot] called, dummy data\n",
      "\n",
      "[ tools ]\n",
      "\n",
      "\n",
      "[ chatbot ]\n",
      "\n",
      "dict_keys(['dummy_data'])\n",
      "[chatbot] called, dummy data\n"
     ]
    }
   ],
   "source": [
    "# Question\n",
    "question = \"Please provide the latest news about the 2024 Nobel Prize in Literature.\"\n",
    "\n",
    "# Define the initial input State\n",
    "input = State(dummy_data=\"Test string\", messages=[(\"user\", question)])\n",
    "\n",
    "# Configure config\n",
    "config = RunnableConfig(\n",
    "    recursion_limit=10,  # Visit up to 10 nodes; beyond that, RecursionError will occur\n",
    "    configurable={\"thread_id\": \"1\"},  # Set the thread ID\n",
    "    tags=[\"my-rag\"],  # Tag\n",
    ")\n",
    "\n",
    "for event in graph.stream(\n",
    "    input=input,\n",
    "    config=config,\n",
    "    output_keys=[\"dummy_data\"],  # Try adding messages!\n",
    "):\n",
    "    for key, value in event.items():\n",
    "        # key is the node name\n",
    "        print(f\"\\n[ {key} ]\\n\")\n",
    "\n",
    "        # If dummy_data exists\n",
    "        if value:\n",
    "            # value is the node output\n",
    "            print(value.keys())\n",
    "            if \"dummy_data\" in value:\n",
    "                print(value[\"dummy_data\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "9cecf357",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[ chatbot ]\n",
      "\n",
      "\n",
      "\n",
      "[ tools ]\n",
      "\n",
      "[{\"title\": \"We Do Not Part is Han Kang’s most ambitious work yet\", \"link\": \"https://chicagoreader.com/books/book-review/we-do-not-part-han-kang/&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQICBAC&usg=AOvVaw3jYW04OKoIxGH_sgMIenuZ\"}, {\"title\": \"2024 Nobel Prize for Literature #KoreaNetPerson of the Year #Vegetarian #TheBoy Is ComingNovelist H..\", \"link\": \"https://www.mk.co.kr/en/culture/11206720&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQICRAC&usg=AOvVaw190Hz6ubn7ksSuhWWP7O_P\"}, {\"title\": \"Author Han Kang receives Nobel Prize in Literature at awards ceremony in Stockholm\", \"link\": \"https://koreajoongangdaily.joins.com/news/2024-12-11/culture/books/Author-Han-Kang-receives-Nobel-Prize-in-Literature-at-awards-ceremony-in-Stockholm/2197328&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQIAxAC&usg=AOvVaw37fTur2s0T9Bc6uTF0-fj7\"}, {\"title\": \"S. Korea's Han Kang receives Nobel literature prize amid turmoil at home\", \"link\": \"https://english.kyodonews.net/news/2024/12/45926507c4d5-s-korea-writer-receives-nobel-prize-amid-political-turmoil-at-home.html&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQIBxAC&usg=AOvVaw3v-DiyzN-XftJHXXJxFSAI\"}, {\"title\": \"Han Kang asserts literature combats life-destructive acts - CHOSUNBIZ\", \"link\": \"https://biz.chosun.com/en/en-society/2024/12/11/CAGKFC32LFEN3DIXJH4HI72M64/&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQIBBAC&usg=AOvVaw1QBF6iIuC3Z60lOhFgm5TR\"}]\n",
      "\n",
      "[ chatbot ]\n",
      "\n",
      "Here are the latest news articles regarding the 2024 Nobel Prize in Literature:\n",
      "\n",
      "1. **We Do Not Part is Han Kang’s most ambitious work yet**  \n",
      "   [Read more](https://chicagoreader.com/books/book-review/we-do-not-part-han-kang/&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQICBAC&usg=AOvVaw3jYW04OKoIxGH_sgMIenuZ)\n",
      "\n",
      "2. **2024 Nobel Prize for Literature: #KoreaNetPerson of the Year #Vegetarian #TheBoy Is Coming Novelist Han Kang**  \n",
      "   [Read more](https://www.mk.co.kr/en/culture/11206720&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQICRAC&usg=AOvVaw190Hz6ubn7ksSuhWWP7O_P)\n",
      "\n",
      "3. **Author Han Kang receives Nobel Prize in Literature at awards ceremony in Stockholm**  \n",
      "   [Read more](https://koreajoongangdaily.joins.com/news/2024-12-11/culture/books/Author-Han-Kang-receives-Nobel-Prize-in-Literature-at-awards-ceremony-in-Stockholm/2197328&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQIAxAC&usg=AOvVaw37fTur2s0T9Bc6uTF0-fj7)\n",
      "\n",
      "4. **S. Korea's Han Kang receives Nobel literature prize amid turmoil at home**  \n",
      "   [Read more](https://english.kyodonews.net/news/2024/12/45926507c4d5-s-korea-writer-receives-nobel-prize-amid-political-turmoil-at-home.html&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQIBxAC&usg=AOvVaw3v-DiyzN-XftJHXXJxFSAI)\n",
      "\n",
      "5. **Han Kang asserts literature combats life-destructive acts**  \n",
      "   [Read more](https://biz.chosun.com/en/en-society/2024/12/11/CAGKFC32LFEN3DIXJH4HI72M64/&ved=2ahUKEwj2h9CFxYmLAxV6klYBHcDiKqgQxfQBegQIBBAC&usg=AOvVaw1QBF6iIuC3Z60lOhFgm5TR)\n",
      "\n",
      "These articles discuss Han Kang's work and her recent receipt of the Nobel Prize amid various contexts and challenges.\n"
     ]
    }
   ],
   "source": [
    "# Question\n",
    "question = \"Please provide the latest news about the 2024 Nobel Prize in Literature.\"\n",
    "\n",
    "# Define the initial input State\n",
    "input = State(dummy_data=\"Test string\", messages=[(\"user\", question)])\n",
    "\n",
    "# Configure config\n",
    "config = RunnableConfig(\n",
    "    recursion_limit=10,  # Visit up to 10 nodes; beyond that, RecursionError will occur\n",
    "    configurable={\"thread_id\": \"1\"},  # Set the thread ID\n",
    "    tags=[\"my-rag\"],  # Tag\n",
    ")\n",
    "\n",
    "for event in graph.stream(\n",
    "    input=input,\n",
    "    config=config,\n",
    "    output_keys=[\"messages\"],  # Only output messages\n",
    "):\n",
    "    for key, value in event.items():\n",
    "        # If messages exist\n",
    "        if value and \"messages\" in value:\n",
    "            # key is the node name\n",
    "            print(f\"\\n[ {key} ]\\n\")\n",
    "            # Print the content of the last message\n",
    "            print(value[\"messages\"][-1].content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f6be39d",
   "metadata": {},
   "source": [
    "### The stream_mode Option\n",
    "\n",
    "The ```stream_mode``` option is used to specify the streaming output mode.\n",
    "\n",
    "- ```values```: Outputs the current state values at each step\n",
    "- ```updates```: Outputs only state updates at each step (default)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86f700fc",
   "metadata": {},
   "source": [
    "### stream_mode = \"values\"\n",
    "\n",
    "In ```values``` mode, the current state values of each step are output.\n",
    "\n",
    "**Note**\n",
    "\n",
    "```event.items()```\n",
    "\n",
    "- ```key```: Key of the State\n",
    "- ```value```: The value corresponding to the key of the State"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "89eff21c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[ messages ]\n",
      "\n",
      "Number of messages: 1\n",
      "\n",
      "[ dummy_data ]\n",
      "\n",
      "==============================  Step  ==============================\n",
      "\n",
      "[ messages ]\n",
      "\n",
      "Number of messages: 2\n",
      "\n",
      "[ dummy_data ]\n",
      "\n",
      "==============================  Step  ==============================\n",
      "\n",
      "[ messages ]\n",
      "\n",
      "Number of messages: 3\n",
      "\n",
      "[ dummy_data ]\n",
      "\n",
      "==============================  Step  ==============================\n",
      "\n",
      "[ messages ]\n",
      "\n",
      "Number of messages: 4\n",
      "\n",
      "[ dummy_data ]\n",
      "\n",
      "==============================  Step  ==============================\n"
     ]
    }
   ],
   "source": [
    "# Question\n",
    "question = \"Please provide the latest news about the 2024 Nobel Prize in Literature.\"\n",
    "\n",
    "# Define the initial input State\n",
    "input = State(dummy_data=\"Test string\", messages=[(\"user\", question)])\n",
    "\n",
    "# Configure config\n",
    "config = RunnableConfig(\n",
    "    recursion_limit=10,  # Visit up to 10 nodes; beyond that, RecursionError will occur\n",
    "    configurable={\"thread_id\": \"1\"},  # Set the thread ID\n",
    "    tags=[\"my-rag\"],  # Tag\n",
    ")\n",
    "\n",
    "# Stream output in values mode\n",
    "for event in graph.stream(\n",
    "    input=input,\n",
    "    stream_mode=\"values\",  # Default\n",
    "):\n",
    "    for key, value in event.items():\n",
    "        # key is the State key\n",
    "        print(f\"\\n[ {key} ]\\n\")\n",
    "        if key == \"messages\":\n",
    "            print(f\"Number of messages: {len(value)}\")\n",
    "    print(\"===\" * 10, \" Step \", \"===\" * 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef295683",
   "metadata": {},
   "source": [
    "### stream_mode = \"updates\"\n",
    "\n",
    "In ```updates``` mode, only the updated State for each step is output.\n",
    "\n",
    "- The output is a dictionary whose key is the node name, and values are the updated outputs.\n",
    "\n",
    "**Note**\n",
    "\n",
    "```event.items()```\n",
    "\n",
    "- ```key```: The node name\n",
    "- ```value```: The output of that node in dictionary form (it can have multiple key-value pairs)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "2124b916",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[ chatbot ]\n",
      "\n",
      "dict_keys(['messages', 'dummy_data'])\n",
      "Number of messages: 1\n",
      "==============================  Step  ==============================\n",
      "\n",
      "[ tools ]\n",
      "\n",
      "dict_keys(['messages'])\n",
      "Number of messages: 1\n",
      "==============================  Step  ==============================\n",
      "\n",
      "[ chatbot ]\n",
      "\n",
      "dict_keys(['messages', 'dummy_data'])\n",
      "Number of messages: 1\n",
      "==============================  Step  ==============================\n"
     ]
    }
   ],
   "source": [
    "# Question\n",
    "question = \"Please provide the latest news about the 2024 Nobel Prize in Literature.\"\n",
    "\n",
    "# Define the initial input State\n",
    "input = State(dummy_data=\"Test string\", messages=[(\"user\", question)])\n",
    "\n",
    "# Configure config\n",
    "config = RunnableConfig(\n",
    "    recursion_limit=10,  # Visit up to 10 nodes; beyond that, RecursionError will occur\n",
    "    configurable={\"thread_id\": \"1\"},  # Set the thread ID\n",
    "    tags=[\"my-rag\"],  # Tag\n",
    ")\n",
    "\n",
    "# Stream output in updates mode\n",
    "for event in graph.stream(\n",
    "    input=input,\n",
    "    stream_mode=\"updates\",  # Default\n",
    "):\n",
    "    for key, value in event.items():\n",
    "        # key is the node name\n",
    "        print(f\"\\n[ {key} ]\\n\")\n",
    "\n",
    "        # value is the output of the node\n",
    "        print(value.keys())\n",
    "\n",
    "        # The state is stored in dict form under the keys of 'values'\n",
    "        if \"messages\" in value:\n",
    "            print(f\"Number of messages: {len(value['messages'])}\")\n",
    "    print(\"===\" * 10, \" Step \", \"===\" * 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d98b21a8",
   "metadata": {},
   "source": [
    "### The interrupt_before and interrupt_after Options\n",
    "\n",
    "The ```interrupt_before``` and ```interrupt_after``` options are used to specify when to interrupt streaming.\n",
    "\n",
    "- ```interrupt_before```: Interrupt streaming before the specified node\n",
    "- ```interrupt_after```: Interrupt streaming after the specified node\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "6f583a38",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[chatbot]\n",
      "\n",
      "dict_keys(['messages', 'dummy_data'])\n",
      "[AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_LL5xawSetOyJGml15BVMzGha', 'function': {'arguments': '{\"query\":\"2024 Nobel Prize in Literature\"}', 'name': 'search_keyword'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 58, 'total_tokens': 78, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_bd83329f63', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-022ff197-23e6-42de-82e4-d5d57f15d7e7-0', tool_calls=[{'name': 'search_keyword', 'args': {'query': '2024 Nobel Prize in Literature'}, 'id': 'call_LL5xawSetOyJGml15BVMzGha', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 20, 'total_tokens': 78, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]\n",
      "Number of messages: 1\n",
      "==============================  Step  ==============================\n",
      "\n",
      "[__interrupt__]\n",
      "\n",
      "==============================  Step  ==============================\n"
     ]
    }
   ],
   "source": [
    "# Question\n",
    "question = \"Please provide the latest news about the 2024 Nobel Prize in Literature.\"\n",
    "\n",
    "# Define the initial input State\n",
    "input = State(dummy_data=\"Test string\", messages=[(\"user\", question)])\n",
    "\n",
    "# Configure config\n",
    "config = RunnableConfig(\n",
    "    recursion_limit=10,  # Visit up to 10 nodes; beyond that, RecursionError will occur\n",
    "    configurable={\"thread_id\": \"1\"},  # Set the thread ID\n",
    "    tags=[\"my-rag\"],  # Tag\n",
    ")\n",
    "\n",
    "for event in graph.stream(\n",
    "    input=input,\n",
    "    config=config,\n",
    "    stream_mode=\"updates\",  # Default\n",
    "    interrupt_before=[\"tools\"],  # Interrupt before the 'tools' node\n",
    "):\n",
    "    for key, value in event.items():\n",
    "        # key is the node name\n",
    "        print(f\"\\n[{key}]\\n\")\n",
    "\n",
    "        # value is the node's output\n",
    "        if isinstance(value, dict):\n",
    "            print(value.keys())\n",
    "            if \"messages\" in value:\n",
    "                print(value[\"messages\"])\n",
    "\n",
    "        # The state is stored as a dict (keys of 'values')\n",
    "        if \"messages\" in value:\n",
    "            print(f\"Number of messages: {len(value['messages'])}\")\n",
    "    print(\"===\" * 10, \" Step \", \"===\" * 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "33dc67a2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[__interrupt__]\n",
      "\n",
      "dict_keys(['messages', 'dummy_data'])\n",
      "[AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_isKC85TbbCIdWDhhQiWbUsNv', 'function': {'arguments': '{\"query\":\"2024 Nobel Prize in Literature\"}', 'name': 'search_keyword'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 20, 'prompt_tokens': 58, 'total_tokens': 78, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_72ed7ab54c', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-e06fd180-ec4e-4773-899c-9414072be3ca-0', tool_calls=[{'name': 'search_keyword', 'args': {'query': '2024 Nobel Prize in Literature'}, 'id': 'call_isKC85TbbCIdWDhhQiWbUsNv', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 20, 'total_tokens': 78, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]\n",
      "Number of messages: 1\n",
      "\n",
      "[__interrupt__]\n",
      "\n",
      "dict_keys(['messages'])\n",
      "[ToolMessage(content='[{\"title\": \"We Do Not Part is Han Kang’s most ambitious work yet\", \"link\": \"https://chicagoreader.com/books/book-review/we-do-not-part-han-kang/&ved=2ahUKEwjlgY6XxYmLAxWIglYBHfzBAeIQxfQBegQIABAC&usg=AOvVaw3OvrSf4pMr7lptlLN_vUn9\"}, {\"title\": \"2024 Nobel Prize for Literature #KoreaNetPerson of the Year #Vegetarian #TheBoy Is ComingNovelist H..\", \"link\": \"https://www.mk.co.kr/en/culture/11206720&ved=2ahUKEwjlgY6XxYmLAxWIglYBHfzBAeIQxfQBegQICRAC&usg=AOvVaw2hwLfAERiJ1CNc1PSxSYb3\"}, {\"title\": \"S. Korea\\'s Han Kang receives Nobel literature prize amid turmoil at home\", \"link\": \"https://english.kyodonews.net/news/2024/12/45926507c4d5-s-korea-writer-receives-nobel-prize-amid-political-turmoil-at-home.html&ved=2ahUKEwjlgY6XxYmLAxWIglYBHfzBAeIQxfQBegQIBxAC&usg=AOvVaw0B_087AxDtCmUrSHxGId_P\"}, {\"title\": \"Olga Tokarczuk attends 2024 Nobel Prize ceremony as special guest - English Section\", \"link\": \"https://www.polskieradio.pl/395/7791/artykul/3457545,olga-tokarczuk-attends-2024-nobel-prize-ceremony-as-special-guest&ved=2ahUKEwjlgY6XxYmLAxWIglYBHfzBAeIQxfQBegQIAhAC&usg=AOvVaw2mVeaIHVC89aBgMoIHWAc8\"}, {\"title\": \"Han Kang asserts literature combats life-destructive acts - CHOSUNBIZ\", \"link\": \"https://biz.chosun.com/en/en-society/2024/12/11/CAGKFC32LFEN3DIXJH4HI72M64/&ved=2ahUKEwjlgY6XxYmLAxWIglYBHfzBAeIQxfQBegQIBRAC&usg=AOvVaw367b4QKTF5jqlbLcVgGYqV\"}]', name='search_keyword', id='9055fb49-3a96-43e2-b005-e99baaf4ce72', tool_call_id='call_isKC85TbbCIdWDhhQiWbUsNv')]\n",
      "Number of messages: 1\n",
      "\n",
      "[__interrupt__]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Question\n",
    "question = \"Please provide the latest news about the 2024 Nobel Prize in Literature.\"\n",
    "\n",
    "# Define the initial input State\n",
    "input = State(dummy_data=\"Test string\", messages=[(\"user\", question)])\n",
    "\n",
    "# Configure config\n",
    "config = RunnableConfig(\n",
    "    recursion_limit=10,  # Visit up to 10 nodes; beyond that, RecursionError will occur\n",
    "    configurable={\"thread_id\": \"1\"},  # Set the thread ID\n",
    "    tags=[\"my-rag\"],  # Tag\n",
    ")\n",
    "\n",
    "for event in graph.stream(\n",
    "    input=input,\n",
    "    config=config,\n",
    "    stream_mode=\"updates\",\n",
    "    interrupt_after=[\"tools\"],  # Interrupt after executing 'tools'\n",
    "):\n",
    "    for value in event.values():\n",
    "        # key is the node name\n",
    "        print(f\"\\n[{key}]\\n\")\n",
    "\n",
    "        if isinstance(value, dict):\n",
    "            # value is the node's output\n",
    "            print(value.keys())\n",
    "            if \"messages\" in value:\n",
    "                print(value[\"messages\"])\n",
    "\n",
    "        # The state is stored as a dict (keys of 'values')\n",
    "        if \"messages\" in value:\n",
    "            print(f\"Number of messages: {len(value['messages'])}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial-bMU5IxA3-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
